<template>
  <div class="chart-wrapper">
    <div class="errorTip" v-if="errorTip">
      <img src="@/assets/exclamation.png" alt="" />
      <span>{{ errorText }}</span>
      <el-button
        class="eig-btn"
        style="width: 45%"
        type="primary"
        @click="back()"
        >回退</el-button
      >
    </div>
    <div class="errorTip" v-if="connectErrorTip && !errorTip">
      <img src="@/assets/exclamation.png" alt="" />
      <span>连接异常断开</span>
      <el-button
        class="eig-btn"
        style="width: 45%"
        type="primary"
        @click="back()"
        >回退</el-button
      >
    </div>
    <div class="errorTip" v-if="successTip">
      <img src="@/assets/exclamation.png" alt="" />
      <span>重新连接</span>
      <el-button
        class="eig-btn"
        style="width: 45%"
        type="primary"
        @click="init()"
        >确认</el-button
      >
    </div>
    <div class="chart-prospect" v-if="prospect">
      <img src="@/assets/loading.gif" alt="" />
    </div>
    <div id="media">
      <audio ref="audio" id="audio"></audio>
      <div id="webgl" class="preview-canvas"></div>
    </div>

    <div class="option" style="display: none">
      <input id="use-stun" type="checkbox" checked />
      <label for="use-stun">对话数字人</label>
    </div>

    <div class="chart-header"></div>
    <div class="start-btn" @click="toggleOpen"></div>
    <div
      class="chart-scrollbar-wrapper"
      ref="msgWrapper"
      :class="{ extra: isExtra }"
    >
      <el-scrollbar
        :style="{ opacity: chartList.length ? 1 : 0 }"
        class="chart-scrollbar"
        ref="scrollbar"
      >
        <ul class="msg-list" ref="msgList">
          <!-- <li class="introduce-item">
          <div class="introduce">
            <div>你可以这样问我：</div>
            <div>广研什么时候成立的呢</div>
            <div>广研什么时候成立的呢</div>
            <div>广研什么时候成立的呢</div>
          </div>
        </li> -->
          <li class="msg-item" v-for="(item, index) in chartList" :key="index">
            <div class="msg" :class="item.type" @click="playRecording(item)">
              <span v-if="!item.icon">{{ item.text }}</span>
              <img
                v-if="item.icon && item.type === 'send'"
                class="point"
                src="@/assets/whitePoint.gif"
                alt=""
              />
              <img
                v-if="item.icon && item.type === 'receive'"
                class="point"
                src="@/assets/greenPoint.gif"
                alt=""
              />
            </div>
          </li>
        </ul>
      </el-scrollbar>
    </div>
    <div class="control-wrapper" :class="{ active: isMouseDown }">
      <audio class="audio-box" ref="audioPlayer" controls></audio>
      <div v-if="isMouseDown" class="btn-tip">松手发送，上移取消</div>
      <div
        class="voice-wrapper"
        :class="{ active: isMouseDown, leave: isTouchLeave }"
      >
        <div
          class="tounch-btn"
          ref="voice"
          @mousedown="mouseDownControl"
          @mouseup="mouseUpControl"
          @mouseleave="mouseOutControl"
          @mousemove="touchOutControl"
          @touchstart="touchstart"
          @touchend="mouseUpControl"
          @touchmove="touchOutControl"
        ></div>
        <div class="touch">
          <img
            v-if="!isMouseDown"
            class="chart-icon"
            src="@/assets/chart-icon.png"
            alt=""
          />
          <span v-if="!isMouseDown">按住说话</span>
          <av-media
            v-else
            class="av-media"
            :media="mediaStream"
            type="frequ"
            frequ-direction="mo"
            :canv-height="60"
            :frequ-lnum="60"
            line-color="#fff"
          />
        </div>
        <!-- <div class="touch-voice" v-if="isMouseDown" ></div> -->
      </div>
    </div>
  </div>
</template>

<script>
import { mapState } from "vuex";
import * as THREE from "@/utils/three.module.js";
import human from "@/assets/video/human.mp4";
import { start, stop, pcConnected } from "@/utils/client.js";
import { Backtop } from "element-ui";
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// var ws = new WebSocket("ws://localhost:8080");
export default {
  data() {
    return {
      recorder: null,
      scriptProcessor: null,
      mediaStream: null,
      isMouseDown: false,
      isCancel: true,
      chartList: [],
      audioChunks: [],
      delay: 200,
      type: "webm", //webm,pcm
      preVideo: null,
      isOpen: false,
      msgList: [
        {
          keyWord: "介绍",
          key: "你好，请做下自我介绍",
          value:
            "您好，我是数字人广小研，来自于农业银行广州研发部，很高兴认识您，请问有什么可以帮您。",
          delay: 1200,
          duration: 8500,
        },
        {
          keyWord: "开户",
          key: "验资户开户有什么要求？",
          value:
            "开设验资账户须向开户银行提供以下资料： 1.开户申请书加盖出资单位公章或由出资人签章。\n  2.出资人、法定代表人（单位负责人）有效身份证件；授权他人办理的，需出具授权书，并同时提供出资人、法定代表人及被授权人有效身份证件。\n  3.工商行政管理部门核发的“企业名称预先核准通知书”或有关部门的批文。出资方为单位的，开立验资账户所提交的开户资料从属当地人民银行的规定。\n  4.单位存款人因增资验资需要开立银行结算账户的，应持股东会或董事会决议等证明文件，在银行开立一个临时存款账户。运营主管审核新核发的营业执照无误后交柜员办理增资户转结算户，并留存新核发的营业执照复印件。",
          delay: 500,
          duration: 57000,
        },
        {
          keyWord: "收费",
          key: "开办验资户收费标准是？",
          value:
            "注册验资户需要收取开户费、银行询征函费及见证企业注册资金费。故当客户咨询到开办验资料户时，应与客户告知上述所有费用。",
          delay: 2000,
          duration: 4000,
        },
        {
          keyWord: "成立",
          key: "广研什么时候成立的呢？",
          value: "广州研发部于2011年成立，为农业银行研发中心首个研发分部。",
          delay: 10,
          duration: 5500,
        },
        {
          keyWord: "部门",
          key: "广研有哪些部门？",
          value:
            "广研目前内设综合管理部、应用支持部、境外产品创新部、开发一部、开发二部、开发三部、测试部等六个处室，人员规模475人。",
          delay: 10,
          duration: 10000,
        },
        {
          keyWord: "职能",
          key: "广研的主要职能有哪些？",
          value:
            "广研主要职能是在研发中心的统一框架下，依据授权制定本研发部的年度工作计划并组织实施。在研发中心的领导下，制定研发部各项制度办法、标准和规范，并组织实施。依据全行信息科技发展规范和信息系统建设方案，承担境外信息系统的产品需求、开发、测试、管理以及全行国际业务、跨境清算、合规筛查等领域的软件开发、产品测试、系统支持等职能。配合海外分行实施应用软件的培训和推广。",
          delay: 500,
          duration: 35000,
        },
      ],
      sendingText: "",
      ws: null,
      // 按下时间
      pressStartTime: null,
      pressStopTime: null,
      isTouchLeave: false,
      prospect: true,
      errorTip: false,
      isExtra: false,
      successTip: true,
      connectErrorTip: false,
      errorText: "数字人加载失败",
    };
  },
  computed: {},
  watch: {},
  created() {
    let isHome = localStorage.getItem("home") == "true" ? true : false;
    if (isHome) {
      this.successTip = false;
    }
    localStorage.setItem("home", false);
    this.errorTip = false;
    this.w_h = 1080 / 1920;
  },
  mounted() {
    if (!this.successTip) {
      this.init();
    }
  },
  beforeDestroy() {
    stop();
    if (this.mediaStream) {
      this.mediaStream.getTracks().forEach((track) => {
        track.stop();
      });
      this.mediaStream = null;
    }
    this.ws && this.ws.close();
  },
  methods: {
    init() {
      this.successTip = false;
      this.onlineTimer && clearTimeout(this.onlineTimer);
      this.onlineTimer = setTimeout(() => {
        this.errorText = "数字人加载超时";
        this.errorTip = true;
      }, 20000);
      this.$axios.get("/api2/onlineCount").then((res) => {
        if (res.data.data.onlineCount) {
          this.$message.error("人数已满，请稍后再试！");
          this.$router.push("/");
        } else {
          this.initWs()
            .then(() => {
              this.initVideo();
              this.renderHuman();
              start(this.preVideo)
                .then(() => {
                  this.onlineTimer && clearTimeout(this.onlineTimer);
                  this.prospect = false;
                })
                .catch(() => {
                  this.errorText = "数字人加载失败";
                  this.errorTip = true;
                });
            })
            .catch(() => {
              this.errorText = "Websocket证书无效";
              this.errorTip = true;
            });
        }
      });
    },
    initWs() {
      return new Promise((resolve, reject) => {
        this.ws = new WebSocket("wss://106.53.187.94:8001");
        this.ws.onopen = () => {
          console.log("连接打开");
          // this.ws.send("Hello, server!");
          resolve();
          setTimeout(() => {
            this.ws.send("1");
          }, 100);
        };

        this.ws.onmessage = (e) => {
          console.log("收到消息：" + e.data);
          let data = JSON.parse(e.data);

          if (data.code === 200) return;
          if (data.type === "ping") return;
          if (data.status == 1) {
            this.receiveData(this.sendingText);
          }
        };

        this.ws.onerror = (event) => {
          console.log("event", event);
          console.log("错误发生");
          reject("websocket连接错误");
        };

        this.ws.onclose = () => {
          this.connectErrorTip = true;
          stop();
          if (this.mediaStream) {
            this.mediaStream.getTracks().forEach((track) => {
              track.stop();
            });
            this.mediaStream = null;
          }
          console.log("连接关闭");
        };
      });
    },
    goBack(text = "网络超时，请稍后再试！") {
      this.$message.error(text);
      setTimeout(() => {
        this.$router.push("/");
      }, 3000);
    },
    back() {
      this.$router.go(-1);
    },
    retry() {
      this.$router.go(0);
    },
    // 隐藏按钮开始流程
    toggleOpen() {
      if (this.isOpen) {
        this.isOpen = false;
        // this.preVideo.pause();
        stop();
      } else {
        this.autoClick();
        this.isOpen = true;
        // this.preVideo.currentTime = 0;
        // this.preVideo.play();
        start(this.preVideo);
      }
    },
    // 自动点击录音
    autoClick() {
      setTimeout(() => {
        this.mouseDownControl();
        setTimeout(() => {
          this.mouseUpControl();
        }, 2000);
      }, 3500);
    },
    // 初始化视频
    initVideo() {
      this.preVideo = document.createElement("video");
      this.preVideo.muted = true;
      this.preVideo.play().catch((e) => {
        console.log("播放失败", e);
      });
    },
    // 渲染数字人
    renderHuman() {
      window.onresize = function () {
        const canvasHeight = window.innerHeight * 0.94;
        const canvasWidth = canvasHeight * this.w_h;
        renderer.setSize(canvasWidth, canvasHeight);
        camera.aspect = canvasWidth / canvasHeight;
        camera.updateProjectionMatrix();
      };

      // const canvasWidth = 352;
      // const canvasHeight = 1280;
      const canvasHeight = window.innerHeight * 0.94;
      const canvasWidth = canvasHeight * this.w_h;

      const camera = new THREE.OrthographicCamera(
        canvasWidth / -2,
        canvasWidth / 2,
        canvasHeight / 2,
        canvasHeight / -2,
        1,
        1000
      );
      camera.position.z = 1000;

      const renderer = new THREE.WebGLRenderer({
        antialias: false,
        alpha: true,
      });
      renderer.outputColorSpace = THREE.SRGBColorSpace;
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(canvasWidth, canvasHeight);
      renderer.setClearColor(0x000000, 0);
      document.getElementById("webgl").appendChild(renderer.domElement);

      const scene = new THREE.Scene();
      const textureLoader = new THREE.TextureLoader();
      const texture = new THREE.VideoTexture(this.preVideo);

      texture.flipY = true;
      texture.colorSpace = THREE.SRGBColorSpace;
      const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight);
      const vertexShader = `
  varying vec2 vUv;
	void main() {
		vUv = uv;
		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
	}`;
      const fragmentShader = `
  uniform sampler2D videoTexture;
	uniform float threshold;
	varying vec2 vUv;
	void main() {
		vec4 color = texture2D(videoTexture, vUv);
		float greenScreen = color.g - max(color.r, color.b);
		float alpha = 1.0 - smoothstep(threshold - 0.05, threshold + 0.05, greenScreen);
		gl_FragColor = vec4(color.rgb, alpha);
	}`;
      var material = new THREE.ShaderMaterial({
        uniforms: {
          videoTexture: {
            value: texture,
          },
          threshold: {
            value: 0.05,
          },
        },
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        side: THREE.DoubleSide,
        transparent: true,
      });
      const mesh = new THREE.Mesh(geometry, material);
      // mesh.position.set(0, -canvasHeight *0.1, 0)

      scene.add(mesh);

      function animate() {
        requestAnimationFrame(animate);
        if (pcConnected) {
          renderer.render(scene, camera);
        }
      }

      animate();
    },

    // 发送文本数据
    sendData(text) {
      let reversedArr = [...this.chartList].reverse();
      let index = reversedArr.findIndex((item) => {
        return item.type === "send";
      });
      if (index == -1) return;
      this.showText(text, this.chartList, this.chartList.length - index - 1);
      this.$nextTick(() => {
        this.judgeExtra();
        this.scrollToBottom();
      });
    },
    // 接受文本数据
    receiveData(text) {
      let reversedArr = [...this.chartList].reverse();
      let index = reversedArr.findIndex((item) => {
        return item.type === "receive";
      });
      if (index == -1) return;
      this.showText(text, this.chartList, this.chartList.length - index - 1);
      this.$nextTick(() => {
        this.scrollToBottom();
      });
    },
    judgeExtra() {
      if (
        this.$refs.msgList.clientHeight >
        this.$refs.msgWrapper.clientHeight - 20
      ) {
        this.isExtra = true;
      }
    },
    // 音频转文本
    audioToText(audioChunks) {
      // 将音频转成wav并且上传到后端解析成文本
      let blob = new Blob(audioChunks, {
        type: "audio/wav;codecs=opus",
      });
      const file = new File([blob], "audio.wav", { type: "audio/wav" });
      const formdata = new FormData();
      formdata.append("file", file);
      this.uploadTimer && clearTimeout(this.uploadTimer);
      this.uploadTimer = setTimeout(() => {
        this.$message.error("网络超时，请稍后再试！");
      }, 10000);
      this.$axios
        .post("/api1/uploadnproc", formdata, {
          headers: {
            "Content-Type": "multipart/form-data",
          },
        })
        .then((res) => {
          let text = res.data.data[1].res;
          if (!text) {
            this.chartList.pop();
            return this.$message.error("识别失败，请再说一遍！");
          }
          const matches = this.msgList.filter((item) =>
            text.includes(item.keyWord)
          );
          this.sendData(text);
          if (matches.length > 0) {
            this.sendingText = matches[0].value;
          } else {
            this.sendingText = "抱歉！这个问题我还不了解！";
          }
          // 把答案发到后端
          setTimeout(() => {
            this.chartList.push({
              type: "receive",
              icon: true,
              text: "",
            });
            this.$nextTick(() => {
              this.scrollToBottom();
            });
            this.humanTimer && clearTimeout(this.humanTimer);
            this.humanTimer = setTimeout(() => {
              this.$message.error("网络超时，请稍后再试！");
            }, 10000);
            this.$axios
              .post("/api2/human", {
                interrupt: true,
                type: "echo",
                text: this.sendingText,
              })
              .then((res) => {
                console.log("human", res);
              })
              .catch((error) => {
                this.$message.error(error.message);
              })
              .finally(() => {
                this.humanTimer && clearTimeout(this.humanTimer);
              });
          }, 1000);
          // if (text) {
          //   this.sendData(this.audioChunks, text);
          // } else {
          //   this.chartList.pop();
          //   this.$message.error("请重新说出您的问题！");
          // }
        })
        .catch((error) => {
          this.chartList.pop();
          this.$message.error(error.message);
        })
        .finally(() => {
          this.uploadTimer && clearTimeout(this.uploadTimer);
        });
    },
    touchstart(event) {
      this.mouseDownControl();
      event.preventDefault();
    },
    // 鼠标按下
    mouseDownControl() {
      console.log("鼠标按下");
      this.isTouchLeave = false;
      this.isMouseDown = true;
      this.audioChunks = [];
      this.pressStartTime = new Date().getTime();
      navigator.mediaDevices
        .getUserMedia({ audio: true })
        .then((stream) => {
          this.mediaStream = stream;
          if (this.type == "webm") {
            this.isCancel = false;
            this.recorder = new MediaRecorder(stream, { type: "audio/wav" });
            this.recorder.start();
            this.recorder.ondataavailable = (e) => {
              if (this.isCancel) return;
              this.audioChunks.push(e.data);
            };
          } else if (this.type == "pcm") {
            const source = audioContext.createMediaStreamSource(stream);
            this.scriptProcessor = audioContext.createScriptProcessor(
              4096,
              1,
              1
            );
            source.connect(this.scriptProcessor);
            this.scriptProcessor.connect(audioContext.destination);
            this.scriptProcessor.onaudioprocess = (e) => {
              var inputData = e.inputBuffer.getChannelData(0);
              this.audioChunks.push(...inputData);
              // this.audioChunks.push(inputData.buffer);
            };
          }
        })
        .catch((err) => {
          console.log(err.message);
        });
    },
    // 鼠标抬起
    mouseUpControl() {
      if (this.isTouchLeave) {
        this.isTouchLeave = false;
        // 手指离开
        this.mouseOutControl();
        return;
      }
      console.log("鼠标抬起");
      this.pressStopTime = new Date().getTime();
      if (this.mediaStream) {
        this.mediaStream.getTracks().forEach((track) => {
          track.stop();
        });
        this.mediaStream = null;
      }
      if (this.type == "webm") {
        if (this.recorder && this.recorder.state == "recording") {
          this.recorder.stop();
          setTimeout(() => {
            // 先发送个空的
            if (this.pressStopTime - this.pressStartTime < 1 * 800) {
              // 2秒
              this.audioChunks = [];
              return this.$message.error("录制时间太短，请重新录制！");
            }
            this.chartList.push({
              type: "send",
              icon: true,
              text: "",
              audioChunks: this.audioChunks,
            });
            this.$nextTick(() => {
              this.scrollToBottom();
            });
            this.audioToText(this.audioChunks);
          }, 1000);

          // this.receiveData();
        }
      } else if (this.type == "pcm") {
        this.sendData(this.audioChunks);
        this.receiveData();
        this.scriptProcessor && this.scriptProcessor.disconnect();
        this.scriptProcessor = null;
      }

      this.isMouseDown = false;
    },
    // 鼠标移出
    mouseOutControl() {
      console.log("鼠标移出");
      this.isMouseDown = false;
      this.pressStartTime = null;
      this.pressStopTime = null;
      if (this.mediaStream) {
        this.mediaStream.getTracks().forEach((track) => {
          track.stop();
        });
        this.mediaStream = null;
      }
      if (this.type == "webm") {
        if (this.recorder && this.recorder.state == "recording") {
          this.recorder.stop();
          this.isCancel = true;
          this.audioChunks = [];
        }
      } else if (this.type == "pcm") {
        this.scriptProcessor && this.scriptProcessor.disconnect();
        this.scriptProcessor = null;
      }
    },
    // 手指移出
    touchOutControl(event) {
      if (this.isOutEl(event, event.target)) {
        console.log("手指移出");
        this.isTouchLeave = true;
      } else {
        this.isTouchLeave = false;
      }
    },
    //判断鼠标或手指是否离开当前元素
    isOutEl(event, el) {
      //计算当前元素位置
      const { height, width, x, y } = el.getBoundingClientRect();
      let clientX;
      let clientY;
      if (event.changedTouches) {
        //touchmove事件
        clientX = event.changedTouches[0].clientX;
        clientY = event.changedTouches[0].clientY;
      } else {
        //mousemove事件
        clientX = event.clientX;
        clientY = event.clientY;
      }

      if (
        clientY > y + height ||
        clientY < y ||
        clientX < x ||
        clientX > x + width
      ) {
        return true;
      }
      return false;
    },
    // 滚动底部
    scrollToBottom() {
      if (this.$refs.scrollbar) {
        const container = this.$refs.scrollbar.$el.querySelector(
          ".el-scrollbar__wrap"
        );
        container.style.scrollBehavior = "smooth";
        container.scrollTop = container.scrollHeight;
      }
    },
    // 显示文本
    showText(text = "", list, i) {
      if (text.length == 0) {
        this.$set(list[i], "text", text);
      }
      this.$set(list[i], "icon", false);
      let index = 0;
      list[i].timer = setInterval(() => {
        if (index < text.length) {
          index++;
          this.$set(list[i], "text", text.substring(0, index));
          this.$nextTick(() => {
            this.judgeExtra();
            this.scrollToBottom();
          });
        } else {
          list[i].timer && clearInterval(list[i].timer);
        }
      }, this.delay);
    },
    // 播放录音
    playRecording(item) {
      if (item.audioChunks) {
        if (this.type == "webm") {
          const superBuffer = new Blob(item.audioChunks, {
            type: "audio/webm",
          });
          this.$refs.audioPlayer.src = URL.createObjectURL(superBuffer);
          this.$refs.audioPlayer.play();
          this.downLoadWav(item.audioChunks);
        } else if (this.type == "pcm") {
          this.palyPCM(item.audioChunks);
        }
      }
    },
    // pcm播放
    palyPCM(pcmData) {
      const sourceNode = audioContext.createBufferSource();
      sourceNode.connect(audioContext.destination);
      // let floatArray = new Float32Array(pcmData);
      let audioBuffer = audioContext.createBuffer(1, pcmData.length, 48000);

      audioBuffer.getChannelData(0).set(pcmData);
      // let audioBufferChannel =
      // for (let i = 0; i < 4096; i++) {
      //   audioBufferChannel[i] = floatArray[i];
      // }
      for (let i = 0; i < array.length; i++) {
        const element = array[i];
      }

      sourceNode.buffer = audioBuffer;

      sourceNode.start();
    },
    // 下载Wav
    downLoadWav(audioChunks) {
      const audioBlob = new Blob(audioChunks, { type: "audio/wav" });
      const url = URL.createObjectURL(audioBlob);
      const a = document.createElement("a");
      a.href = url;
      a.download = "eig.wav";
      a.click();
    },
  },
};
</script>
<style lang="scss" scoped></style>
